package cluster

import (
	"gddgame.cc/galaxy/asteroid"

	"gddgame.cc/galaxy/utils/def"
)

const (
	ContainerRoom = "global"
	ExitEvent     = "exit"
)

type ReadyHandler func()

type Container interface {
	def.ID
	OnReady(f ReadyHandler)

	Join(socket Socket, userId string)
	Leave(socket Socket, userId string)
	Notice(userId string, event interface{}, params ...interface{})

	GetUserRoomId(userId string) string
	GetUserRoom(socket Socket, userId string) *asteroid.Room

	Emit(event interface{}, data ...interface{}) error

	BalanceClient(module string, token string) *Client
	HashClient(module string, token string) *Client

	Hash() *HashCenter
	Balance() *BalanceCenter
	Region() *asteroid.Region
}

type container struct {
	def.Logger

	identity string
	engine   *engine

	region *asteroid.Region
	global *asteroid.Room

	cluster *election

	readyFunc []ReadyHandler

	hash    *HashCenter
	balance *BalanceCenter

	ready bool
	exit  bool
	stop  bool

	exitChan chan string

	number int32
}

func newContainer(e *engine, region *asteroid.Region, identity string) *container {
	global, _ := region.OpenRoom(ContainerRoom)
	s := &container{
		identity: identity,
		engine:   e,
		region:   region,
		global:   global,
		Logger:   e.LoggerWrap("identity", identity),

		readyFunc: make([]ReadyHandler, 0),
		ready:     false,
		exit:      false,
		stop:      false,

		number: 0,
	}

	s.hash = newHash(s)
	s.balance = newBalance(s)

	global.SetThread(false)
	// balance创建client阻塞所有的线程池，导致异步创建server阻塞
	// 设置线程池为空，解决所有线程池被卡住的问题
	global.SetThreadPool(nil)

	global.OnLeave(func(from string) {
		// 机器移除，进行故障处理
		if !s.stop {
			s.balance.recovery(from)
		}
	})
	global.OnFinish(func() {
		// 用于判断是否链接上其他room
		if s.ready {
			return
		}
		s.Cluster()
		s.ready = true
		for key, _ := range s.readyFunc {
			s.readyFunc[key]()
		}
	})
	global.On(ExitEvent, func(message asteroid.Message) {
		s.balance.recovery(message.GetFrom())
	})

	return s
}

func (s *container) ID() string {
	return s.identity
}

func (s *container) Region() *asteroid.Region {
	return s.region
}

func (s *container) OnReady(f ReadyHandler) {
	s.readyFunc = append(s.readyFunc, f)
	if s.ready {
		f()
	}
}

func (s *container) Join(socket Socket, userId string) {
	single := s.GetUserRoom(socket, userId)
	_ = socket.Add(single)
}

func (s *container) Leave(socket Socket, userId string) {
	single := s.GetUserRoom(socket, userId)
	_ = socket.Delete(single)
	single.Close()
}

func (s *container) Hash() *HashCenter {
	return s.hash
}
func (s *container) Balance() *BalanceCenter {
	return s.balance
}

func (s *container) Cluster() *election {
	if s.cluster == nil {
		s.cluster = newElection(s.global, s)
		s.cluster.Run()
	}
	return s.cluster
}

func (s *container) Notice(userId string, event interface{}, params ...interface{}) {
	s.region.Broadcast(s.GetUserRoomId(userId)).Data(params).Send(event)
}

func (s *container) Emit(event interface{}, data ...interface{}) error {
	data = append([]interface{}{s.identity}, data...)
	_, err := s.engine.event.Fire(event, data)
	if err != nil {
		s.Error(err)
	}
	return err
}

func (s *container) BalanceClient(module string, token string) *Client {
	return s.balance.GetClient(module, token)
}

func (s *container) HashClient(module string, token string) *Client {
	return s.hash.GetClient(module, token)
}

func (s *container) GetUserRoomId(userId string) string {
	return "user/" + userId
}

func (s *container) GetUserRoom(socket Socket, userId string) *asteroid.Room {
	r, _ := s.region.OpenRoom(s.GetUserRoomId(userId))
	return r
}

func (s *container) Exit(exit chan string) bool {
	// 停止所有主动节点开启服务
	// 并发送退出命令，触发节点退出的权限转移
	s.exitChan = exit
	s.exit = true
	s.global.Other().Send(ExitEvent)
	if s.balance.waitNumber == 0 {
		return true
	} else {
		s.Debugf("[ Cluster ] Balance wait: %d", s.balance.waitNumber)
	}
	return false
}

func (s *container) onCloseService() {
	if s.exit {
		// 退出过程中，如果waitNumber为0，则完成退出
		s.Debugf("[ Cluster ] Balance wait: %d", s.balance.waitNumber)
		if s.balance.waitNumber == 0 {
			s.exitChan <- s.ID()
		}
	}
}

func (s *container) close() {
	s.stop = true
	s.exit = true
	s.hash.close()
	s.balance.close()
	_ = s.region.CloseRoom(ContainerRoom)
}
